﻿using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Reflection;
using Volo.Abp;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.Modeling;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Validation;
using CommentAttribute = System.ComponentModel.DataAnnotations.CommentAttribute;
using IndexAttribute = System.ComponentModel.DataAnnotations.IndexAttribute;
using ValueGeneratedNeverAttribute = System.ComponentModel.DataAnnotations.ValueGeneratedNeverAttribute;
using MultiTenancyHostSideAttribute = System.ComponentModel.DataAnnotations.MultiTenancyHostSideAttribute;
using MultiKeyAttribute = System.ComponentModel.DataAnnotations.MultiKeyAttribute;
using PrecisionAttribute = System.ComponentModel.DataAnnotations.PrecisionAttribute;
using Volo.Abp.Users;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.Auditing;

namespace Xms.Abp.Storage;

/// <summary>
/// 自动分表
/// https://www.cnblogs.com/coldairarrow/archive/2020/04/20/12733886.html
/// https://www.cnblogs.com/hubro/p/12693868.html
/// http://crl.changqidongli.com/
/// https://github.com/Coldairarrow/EFCore.Sharding
/// 分库
/// https://www.cnblogs.com/leolaw/p/10461156.html
/// https://docs.microsoft.com/zh-cn/ef/core/modeling/dynamic-model
/// https://blog.csdn.net/longtenggenssupreme/article/details/110194283
/// 分库、分表、读写分离
/// https://www.cnblogs.com/xuejiaming/p/15173965.html
/// https://xuejm.gitee.io/sharding-core-doc/guide/introduce/
/// https://github.com/xuejmnet/AbpVNextShardingTodoApp
/// </summary>
/// <typeparam name="TStorage"></typeparam>
/// [ReplaceDbContext(typeof(IIdentityDbContext))]
public abstract class XmsStorage<TStorage>
    : AbpDbContext<TStorage>, IXmsStorage
    where TStorage : XmsStorage<TStorage>
{
    public virtual int? CommandTimeout { get; }

    public static int? GlobalCommandTimeout { get; }

    protected IServiceProvider ServiceProvider { get; }

    private ICurrentUser _CurrentUser;
    protected ICurrentUser CurrentUser
    {
        get
        {
            if (_CurrentUser == null)
            {
                _CurrentUser = ServiceProvider.GetRequiredService<ICurrentUser>();
            }
            return _CurrentUser;
        }
    }

    //public XmsStorage(XmsStorageOptions<TStorage> options)
    //    : base(options)
    //{

    //}
    public XmsStorage(DbContextOptions<TStorage> options, IServiceProvider serviceProvider)
        : base(options)
    {
        ServiceProvider = serviceProvider;
        if (CommandTimeout.HasValue || GlobalCommandTimeout.HasValue)
        {
            Database.SetCommandTimeout(CommandTimeout ?? GlobalCommandTimeout);
        }
    }

    protected virtual void SetTenantIdIfNull(EntityEntry entry)
    {
        if (entry.Entity is IMultiTenant entityWithTenantId
            && entityWithTenantId.TenantId == null
            && CurrentTenant.Id.HasValue
            && IsMultiTenantFilterEnabled)
        {
            ObjectHelper.TrySetProperty(entityWithTenantId, e => e.TenantId, () => CurrentTenant.Id);
        }
    }

    protected virtual void SetLastModification(EntityEntry entry)
    {
        if (entry.Entity is IModificationAuditedObject entityWithLastModification)
        {
            entityWithLastModification.LastModifierId = CurrentUser?.Id;
            entityWithLastModification.LastModificationTime = DateTime.Now;
        }
        else if (entry.Entity is IHasModificationTime entityWithLastModificationTime)
        {
            entityWithLastModificationTime.LastModificationTime = DateTime.Now;
        }
    }

    protected override void ApplyAbpConceptsForAddedEntity(EntityEntry entry)
    {
        SetTenantIdIfNull(entry);
        base.ApplyAbpConceptsForAddedEntity(entry);
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        var storageType = typeof(TStorage);

        var storageOptionsType = typeof(IOptions<>).MakeGenericType(typeof(XmsStorageConfigurationOptions<>).MakeGenericType(storageType));
        var storageOptions = ServiceProvider.GetRequiredService(storageOptionsType) as IOptions<AbpModelBuilderConfigurationOptions>;

        var tables = storageType.GetProperties().Where(r => r.PropertyType.IsGenericType && r.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>)).Select(r => r.PropertyType.GenericTypeArguments.First());

        foreach (var table in tables)
        {
            OnTableModelCreating(builder, table, storageOptions);
        }

        builder.TryConfigureObjectExtensions<TStorage>();
    }

    protected virtual void OnTableModelCreating(ModelBuilder builder, Type table, IOptions<AbpModelBuilderConfigurationOptions> storageOptions)
    {
        var navigationProperties = new List<Type>();

        var tableOptionsType = typeof(IOptions<>).MakeGenericType(typeof(XmsStorageEntityConfigurationOptions<>).MakeGenericType(table));
        var tableOptions = ServiceProvider.GetRequiredService(tableOptionsType) as IOptions<AbpModelBuilderConfigurationOptions>;

        if (tableOptions.Value == null || tableOptions.Value.TablePrefix.IsNullOrWhiteSpace())
        {
            tableOptions = storageOptions;
        }

        var hasMultiTenancyHostSideAttribute = table.GetCustomAttribute<MultiTenancyHostSideAttribute>() != null;
        if (!hasMultiTenancyHostSideAttribute || builder.IsHostDatabase())
        {
            builder.Entity(table, b =>
            {
                var tableAttr = table.GetCustomAttribute<TableAttribute>();
                var tableComentAttr = table.GetCustomAttribute<CommentAttribute>();
                var tableName = (tableOptions.Value?.TablePrefix ?? "") + (tableAttr?.Name ?? table.Name).Replace("yEntity", "ies").Replace("esEntity", "es").Replace("sEntity", "es").Replace("Entity", "s").Replace("yModel", "ies").Replace("esModel", "es").Replace("sModel", "es").Replace("Model", "s");
                var schemaName = tableOptions.Value?.Schema ?? tableAttr?.Schema;
                b.ToTable(tableName, schemaName);

                var multiKeyAttr = table.GetCustomAttribute<MultiKeyAttribute>();
                if (multiKeyAttr != null)
                {
                    b.HasKey(multiKeyAttr.PropertyNames.ToArray());
                }
                b.ConfigureByConventionWithIndex();

                var properties = table.GetProperties();//.Where(r => r.GetCustomAttribute<CommentAttribute>() != null);

                foreach (var property in properties)
                {
                    var propertyForeignKeyAttr = property.GetCustomAttribute<ForeignKeyAttribute>();
                    if (property.PropertyType.Name == typeof(ICollection<>).Name && propertyForeignKeyAttr != null)
                    {
                        b.HasMany(property.Name).WithOne().HasForeignKey(propertyForeignKeyAttr.Name).IsRequired();
                        navigationProperties.Add(property.PropertyType.GetGenericArguments()[0]);
                    }
                    else
                    {
                        var notMapped = property.GetCustomAttribute<NotMappedAttribute>() != null;
                        if (!notMapped)
                        {
                            var propertyComentAttr = property.GetCustomAttribute<CommentAttribute>();
                            var hasValueGeneratedNeverAttr = property.GetCustomAttribute<ValueGeneratedNeverAttribute>() != null;
                            var p = b.Property(property.Name);
                            if (property.PropertyType == typeof(decimal))
                            {
                                var precisionAttr = property.GetCustomAttribute<PrecisionAttribute>();
                                if (precisionAttr == null)
                                {
                                    p = p.HasPrecision(18, 4);
                                }
                                else if (precisionAttr.Scale.HasValue)
                                {
                                    p = p.HasPrecision(precisionAttr.Precision, precisionAttr.Scale.Value);
                                }
                                else
                                {
                                    p = p.HasPrecision(precisionAttr.Precision);
                                }
                            }
                            if (propertyComentAttr != null)
                            {
                                p = p.HasComment(propertyComentAttr.Comment);
                            }
                            if (hasValueGeneratedNeverAttr)
                            {
                                p.ValueGeneratedNever();
                            }
                        }
                        else
                        {
                            b.Ignore(property.Name);
                        }
                    }
                }

                var indexAttrs = table.GetCustomAttributes<IndexAttribute>();
                foreach (var indexAttr in indexAttrs)
                {
                    var pns = indexAttr.PropertyNames.ToArray();
                    if (!indexAttr.Name.IsNullOrWhiteSpace())
                    {
                        b.HasIndex(pns, indexAttr.Name);
                    }
                    else
                    {
                        b.HasIndex(pns);
                    }
                }

                b.ApplyObjectExtensionMappings();
            });
        }
        foreach (var item in navigationProperties)
        {
            OnTableModelCreating(builder, item, storageOptions);
        }
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);

    }
}
